useQuery 구현하기 3탄 | 커스텀 useInfiniteQuery
2023. 10. 09.
#리액트
2탄에서 useQuery 까지 구현해봤고 이제 useInfiniteQuery 를 구현해보자! useInfiniteQuery 구현하면서 2탄에서 적용하지 않은 useErrorBoundary 를 구현해보자!
🚀 구현한 UI
👨🏻💻 간단한 useInfiniteQuery 구현하기
useInfiniteQuery 기능과 errorBoundary 를 추가했다.
구현 코드는 여기를 참고!
인터페이스
export function useInfiniteQuery<T>( fetcher: (pageIndex: number, perPage: number) => Promise<T>, queryKey: unknown[]; perPage?: number; onSuccess?: (data?: T) => void; onError?: (error?: unknown) => void; useErrorBoundary?: boolean; enabled?: boolean; // suspense?: boolean; // useQuery 에서 구현해서 미구현 }
- 기존 useQuery 에서
perPage
와useErrorBoundary
속성을 제외하곤 같다
- 첫번째 인자인 fetcher에 인자
pageIndex
,perPage
가 추가되었는데 useInfiniteQuery 는 현재 페이지(pageIndex
), 페이지당 요청 개수 (perPage
) 가 필요하므로 추가하였다.
useInfiniteQuery 훅
example4/hooks/useInfiniteQuery.ts
import { useCallback, useEffect, useRef, useState } from 'react'; import { Cache } from '../utils/cache'; export function useInfiniteQuery<T>( fetcher: (pageIndex: number, perPage: number) => Promise<T>, { queryKey, ...option }: useInfiniteFetchOption<T>, ) { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<Error | null>(null); const { enabled = true, // perPage = 10, useErrorBoundary = false, onSuccess, onError, } = option; const [cache] = useState(new Cache<string, InfiniteCache<T>>()); const page = useRef(1); const 쿼리키 = JSON.stringify(queryKey); const fetch = useCallback(async () => { setIsLoading(true); setError(null); try { const cachedData = cache.get(쿼리키) || { data: [], pageIndex: 1, perPage }; const data = await fetcher.apply(null, [page.current, perPage]); cache.set(쿼리키, { // T 로 [] 로 지정할 수 있는데 이를 T 로 인식하게 했는데 타입에서 [] 로 해야 [...data] 할 수 있어 우선 ts-ignore 추가 // @ts-ignore data: [...cachedData.data, ...data], pageIndex: page.current, perPage, }); page.current++; onSuccess && onSuccess(data); } catch (error) { if (error instanceof Error) { setError(error); } onError && onError(error); } finally { setIsLoading(false); } }, []); useEffect(() => { if (!enabled) return; if (cache.has(쿼리키)) return; fetch(); }, [perPage, 쿼리키]); // ErrorBoundary 처리 if (useErrorBoundary && error) { throw new Error(error.message); } const cachedData = cache.get(쿼리키); return { isLoading: isLoading, data: cachedData?.data || [], error, onNextFetch: fetch, }; } type useInfiniteFetchOption<T> = { queryKey: unknown[]; perPage?: number; onSuccess?: (data?: T) => void; onError?: (error?: unknown) => void; useErrorBoundary?: boolean; enabled?: boolean; suspense?: boolean; }; type InfiniteCache<T> = | { data: T; pageIndex: number; perPage: number; } | undefined;
핵심 로직
👉 현재 페이지 값 제어하기
const page = useRef(1);
- page 를 관리하기 위해 useRef 로 관리하였다
👉 페이지네이션 API 처리
- fetch 함수 정의 및 fetcher 로 전달받은 함수에 내부 인자 전달하기
const fetch = useCallback(async () => { setIsLoading(true); setError(null); try { const cachedData = cache.get(쿼리키) || { data: [], pageIndex: 1, perPage }; const data = await fetcher.apply(null, [page.current, perPage]); cache.set(쿼리키, { // T 로 [] 로 지정할 수 있는데 이를 T 로 인식하게 했는데 타입에서 [] 로 해야 [...data] 할 수 있어 우선 ts-ignore 추가 // @ts-ignore data: [...cachedData.data, ...data], pageIndex: page.current, perPage, }); page.current++; onSuccess && onSuccess(data); } catch (error) { if (error instanceof Error) { setError(error); } onError && onError(error); } finally { setIsLoading(false); } } , []);
- fetch 함수에서 기존에 있는 데이터를 가져오기 위해 cachedData 로 조회했다.
const data = await fetcher.apply(null, [page.current, perPage]);
구문을 보면 apply로 함수를 호출하였다
외부에서 주입받는 fetcher(페이지네이션 api 함수) 의 인자는 내부적으로 계속 달라지는 값을 넣어야 하기에 apply 를 이용해서 주입했다.
❓ fetcher(페이지네이션 api 함수) 의 인자는 내부적으로 계속 달라지는 값? 💡 내부에서 현재 페이지인page
로 관리하기 때문에 fetch 함수가 호출될때 마다 달라진page
값을 전달해야함!
❓ apply 메서드란? 💡 apply 메서드는 함수를 호출하는 함수로 인자로this
와arguments
(인자) 를 제공한다. ⇒ 예제에서는page.current 와 perPage
인자를 전달하기 위해 사용했다.
👉 에러바운더리 처리
// ErrorBoundary 처리 if (useErrorBoundary && error) { throw new Error(error.message); }
- 옵션으로 전달한 useErrorBoundary 가 true 이고 error 가 있다면 Error 를 던져서 처리했다.
이로써 useInfiniteQuery 를 아주 간단하게 알아봤다!
추가로 정상적인 부분 뿐만 아니라 로컬 에러, 에러바운더리 부분을 제어해서 확인하고 싶다면 주변에
GYU-TEST
키워드로 남겨났다.해당 글이 도움이 되었으면 좋겠다!!